package com.minivision.fdiot.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import com.minivision.fdiot.entity.Face;
import com.minivision.fdiot.entity.FaceSet;
import com.minivision.fdiot.exception.ErrorType;
import com.minivision.fdiot.exception.FacePlatException;
import com.minivision.fdiot.repository.redis.FaceRepository;
import com.minivision.fdiot.repository.redis.FaceSetRepository;

import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

@Service
@Slf4j
public class FaceCommonService extends BaseService {

	private static final Logger LOGGER = LoggerFactory.getLogger(FaceCommonService.class);

	@Autowired
	private FaceSetRepository setRepository;

	@Autowired
	private FaceRepository faceRepository;

	@Autowired
	private Cache faceCache;
	
	@Autowired
    private StringRedisTemplate stringRedisTemplate; 
	
	@Autowired
	private RedisTemplate<String, float[]> featureTemplate;
	
	/**
	 * 根据facesetToken查找人脸库
	 * 
	 * @param facesetToken
	 * @return
	 * @throws FacePlatException
	 */
	public FaceSet findOneFaceset(String facesetToken) throws FacePlatException {
		FaceSet faceset = setRepository.findOne(facesetToken);
		if (faceset == null) {
			LOGGER.error("faceset:[{}] not exist", facesetToken);
			throw new FacePlatException(ErrorType.FACESET_NOT_EXIST);
		}
		return faceset;
	}

	/**
	 * 根据faceToken查找人脸
	 * 一级缓存ehcache，二级缓存redis
	 * @param faceToken
	 * @return
	 * @throws FacePlatException
	 */
	public Face findOneFace(String faceToken) throws FacePlatException {
		Element element = faceCache.get(faceToken);
		if (element != null) {
			return (Face) element.getObjectValue();
		}

		Face face = findOneFaceFromRedis(faceToken);
		if (face == null) {
			LOGGER.error("face:[{}] not exist", faceToken);
			throw new FacePlatException(ErrorType.FACE_NOT_EXIST);
		}
		return face;
	}
	
	/**
	 * 根据faceToken查找人脸
	 * 从ehcache中查找
	 * @param faceToken
	 * @return
	 * @throws FacePlatException
	 */
	public Face findOneFaceFromCache(String faceToken) {
		Element element = faceCache.get(faceToken);
		if (element != null) {
			return (Face) element.getObjectValue();
		}

		return null;
	}

	/**
	 * 根据faceToken查找人脸
	 * 从redis中查找
	 * @param faceToken
	 * @return
	 */
	public Face findOneFaceFromRedis(String faceToken){
	  String key = getFaceKey(faceToken);
	  float[] feature = featureTemplate.opsForValue().get(key+":feature");
	  if(feature == null){
	    return null;
	  }
	  Face face = new Face();
	  face.setToken(faceToken);
	  face.setFeature(feature);
	  return face;
	}
	
	/**
	 * 增加人脸的引用计数
	 * @param faceToken
	 * @return
	 */
	public int addReferenceCnt(String faceToken){
	  String key = getFaceKey(faceToken);
	  Long ref = stringRedisTemplate.opsForValue().increment(key+":referenceCnt", 1);
	  return ref.intValue();
	}
	
	/**
	 * 减少人脸的引用计数
	 * @param faceToken
	 * @return
	 */
	public int decreaseReferenceCnt(String faceToken){
      String key = getFaceKey(faceToken);
      Long ref = stringRedisTemplate.opsForValue().increment(key+":referenceCnt", -1);
      if(ref.intValue() <= 0){
        stringRedisTemplate.delete(key+":referenceCnt");
      }
      return ref.intValue();
    }
	
	/**
	 * 批量获取人脸的特征值
	 * @param faceTokens
	 * @return
	 */
	public List<float[]> getAllFeatures(Set<String> faceTokens){
	  List<String> keys = faceTokens.stream().map(t -> getFaceKey(t)+":feature").collect(Collectors.toList());
	  List<float[]> list = featureTemplate.opsForValue().multiGet(keys);
	  return list;
	}
	
	/**
	 * 获取某个人脸库下所有的人脸特征值
	 * @param facesetToken
	 * @return
	 */
	@Cacheable(cacheNames="features", key="#facesetToken")
	public Map<String, float[]> getAllFeaturesOfFaceSet(String facesetToken){
	  Set<String> faceTokens = stringRedisTemplate.opsForZSet().rangeByScore(getFaceSetKey(facesetToken), 0, 0);
	  List<float[]> features = getAllFeatures(faceTokens);
	  Map<String, float[]> res = new HashMap<>();
	  int i = 0;
	  for(String face: faceTokens){
	    res.put(face, features.get(i));
	    i++;
	  }
	  return res;
	}
	
	/**
	 * 保存人脸特征值
	 * @param face
	 * @return
	 */
	public Face saveFeature(Face face){
	  String token = face.getToken();
	  String key = getFaceKey(token);
	  featureTemplate.opsForValue().set(key+":feature", face.getFeature());
	  return face;
	}
	
	/**
	 * 删除人脸
	 * @param face
	 * @return
	 */
	public Face delete(Face face){
      String key = getFaceKey(face.getToken());
      featureTemplate.delete(key+":feature");
      faceRepository.delete(face.getToken());
      return face;
    }

	/**
	 * get the key of faceset(Redis Set) in redis
	 * 
	 * @param faceSetToken
	 * @return
	 */
	public String getFaceSetKey(String faceSetToken) {
	  return "facesets:" + faceSetToken + ":faces";
	}
	
	public String getFaceKey(String faceToken){
	  return "faces:"+faceToken;
	}
	
	/**
	 * 批量根据人脸的外部自定义ID获取人脸
	 * @param outerIds
	 * @return
	 */
	public List<Face> findByOuterIds(List<String> outerIds) {
	  List<Face> faces = new ArrayList<>();
	  if (!CollectionUtils.isEmpty(outerIds)) {
        outerIds.stream().forEach((outerId) -> {
          try {
            faces.add(faceRepository.findByOuterId(outerId));
          } catch (Exception e) {
            log.error("find face by outerId failed, outerId:" + outerId, e);
          }
        });
      }
	  return faces;
	}
	
	/**
	 * 保存人脸信息
	 * @param face
	 */
	public void saveFace(Face face) {
	  Face faceInfo = new Face();
	  BeanUtils.copyProperties(face, faceInfo);
	  faceInfo.setFeature(null);
	  faceRepository.save(faceInfo);
	}
	
	/**
	 * 获取人脸信息
	 * @param faceToken
	 * @return
	 */
	public Face findFaceInfo(String faceToken) {
	  return faceRepository.findOne(faceToken);
	}
	
}
